//
//  MusicPlayerManager.swift
//  音乐播放管理器
//  封装了常用的音乐播放功能
//  例如：播放，暂停，继续播放等功能
//  目的就是对外面提供统一的接口
//  好处是内部自由的重构
//  只需要对外部接口不变
//
//  Created by smile on 2019/6/23.
//  Copyright © 2019 ixuea. All rights reserved.
//

import UIKit

//导入媒体模块
import MediaPlayer

class MusicPlayerManager:NSObject {
    static var STATUS = "status"
    
    private static var instance:MusicPlayerManager?
    
    /// 播放状态
    var status:PlayStatus = .none
    
    /// 播放器
    var player:AVPlayer!
    
    /// 当前音乐
    var data:Song!
    
    /// 播放代理
    var delegate:MusicPlayerDelegate? {
        didSet {
            if let _ = self.delegate {
                //有代理
                
                //判断是否有音乐在播放
                if isPlaying() {
                    //有音乐在播放
                    
                    //启动定时器
                    startPublishProgress()
                }
            }else {
                //没有代理
                
                //停止定时器
                stopPublishProgress()
            }
        }
    }
    
    /// 定时器返回的对象
    var playTimeObserve:Any?
    
    /// 播放完成后的回调
    var onComplete:((_ data:Song) -> Void)!
    
    /// 获取单例的播放管理器
    ///
    /// - Returns: <#return value description#>
    static func shared() -> MusicPlayerManager {
        if instance == nil {
            instance = MusicPlayerManager()
        }
        
        return instance!
    }
    
    /// 设置监听器
    func initListeners() {
        //KVO方式监听播放状态
        //KVC:Key-Value Coding,另一种获取对象字段的值，类似字典
        //KVO:Key-Value Observing,建立在KVC基础上，能够观察一个字段值的改变
        player.currentItem?.addObserver(self, forKeyPath: MusicPlayerManager.STATUS, options: .new, context: nil)
        
        //监听播放结束事件
        NotificationCenter.default.addObserver(self, selector: #selector(onComplete(notification:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
    }
    
    /// 移除监听器
    func removeListeners() {
        player.currentItem?.removeObserver(self, forKeyPath: MusicPlayerManager.STATUS)
        
        NotificationCenter.default.removeObserver(self)
    }
    
    /// 播放完毕了回调
    ///
    /// - Parameter notification: <#notification description#>
    @objc func onComplete(notification:Notification) {
        print("MusicPlayerManager onComplete")
        
        onComplete(data)
    }
    
    /// KVO监听回调方法
    ///
    /// - Parameters:
    ///   - keyPath: <#keyPath description#>
    ///   - object: <#object description#>
    ///   - change: <#change description#>
    ///   - context: <#context description#>
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        print("MusicPlayerManager observeValue:\(keyPath)")
        
        //判断监听的字段
        if MusicPlayerManager.STATUS == keyPath {
            //播放状态
            switch player.status {
            case .readyToPlay:
                //准备播放完成了
                self.data!.duration = Float(CMTimeGetSeconds(player.currentItem!.asset.duration))
                
                print("MusicPlayerManager observeValue duration:\(self.data!.duration)")
                
                //回调代理
                delegate?.onPrepared(data)
                
                //更新媒体控制中心信息
                updateMediaInfo()
            case .failed:
                //播放失败了
                status = .error
                
                print("MusicPlayerManager observeValue error")
            default:
                //未知状态
                print("MusicPlayerManager observeValue unknown status")
                break
            }
        }
        
        
    }
    
    /// 初始化
    override init() {
        super.init()
        
        player=AVPlayer.init()
    }
    
    /// 更新系统媒体控制中心信息
    /// 不需要更新进度到控制中心
    /// 他那边会自动倒计时
    func updateMediaInfo() {
        //下载图片
        //这部分可以封装
        //因为其他界面可能也会用
        let manager = SDWebImageManager.shared()
        
        //获取图片地址
        let imageUrl = ResourceUtil.resourceUri(data.banner)
        
        //下载图片
        manager.imageDownloader?.downloadImage(with: URL(string: imageUrl), options: .progressiveDownload, progress: { receivedSize, expectedSize, targetURL in
            //下载进度
            print("MusicPlayerManager updateMediaInfo download image progress:\(receivedSize),total:\(expectedSize)")
        }, completed: { (image, data, error, finished) in
            if let image = image {
                //图片下载完成
                self.setMediaInfo(image)
            }
        })
    }
    
    /// 设置媒体信息到系统的媒体控制中心
    ///
    /// - Parameter image: <#image description#>
    func setMediaInfo(_ image:UIImage) {
        //初始化一个可变字典
        var songInfo:[String:Any] = [:]
        
        //初始化封面
        let albumArt = MPMediaItemArtwork(boundsSize: CGSize(width: 100, height: 100)) { size -> UIImage in
            return image
        }
        
        //设置封面
        songInfo[MPMediaItemPropertyArtwork]=albumArt
        
        //设置歌曲名称
        songInfo[MPMediaItemPropertyTitle]=data.title
        
        //歌手
        songInfo[MPMediaItemPropertyArtist]=data.singer.nickname
        
        //专辑名称
        //由于服务端没有返回专辑的数据
        //所以这里就写死数据就行了
        songInfo[MPMediaItemPropertyAlbumTitle]="这是专辑名称ixuea"
        
        //流派
        songInfo[MPMediaItemPropertyGenre]="这是流派ixuea"
        
        //设置音乐的总时长
        songInfo[MPMediaItemPropertyPlaybackDuration]=data.duration
        
        //设置已经播放的时长
        songInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime]=data.progress
        
        //设置歌词
        songInfo[MPMediaItemPropertyLyrics]="这是歌词ixuea"
        
        //设置到系统
        MPNowPlayingInfoCenter.default().nowPlayingInfo = songInfo
    }
    
    /// 播放
    ///
    /// - Parameters:
    ///   - uri: <#uri description#>
    ///   - song: <#song description#>
    func play(_ uri:String,_ song:Song) {
        //更改播放状态
        status = .playing
        
        //保存音乐对象
        self.data = song
        
        var url:URL!
        if uri.hasPrefix("http") {
            //网络地址
            url = URL(string: uri)!
        } else {
            //本地地址
            url = URL(fileURLWithPath: uri)
        }
        
        //创建一个播放Item
        let playerItem = AVPlayerItem(url: url)
        
        //替换掉原来的播放Item
        player.replaceCurrentItem(with: playerItem)
        
        //播放
        player.play()
        
        //设置监听器
        //因为监听器是针对PlayerItem的
        //所以说播放了音乐在这里设置
        initListeners()
        
        //回调代理
        delegate?.onPlaying(data!)
        
        //启动进度分发定时器
        startPublishProgress()
        
//        if delegate != nil {
//            delegate!.onPlaying(data!)
//        }
        
        //获取歌词
        if data.lyric == nil {
            //只有没有歌词的时候在请求歌词
            //好处是对于同一首音乐不会重复请求
            Api.shared.songDetail(data.id).subscribeOnSuccess { data in
                if let data = data?.data {
                    //但是不一定有歌词
                    self.data.style = data.style
                    self.data.lyric = data.lyric
                    
                    //回调代理
                    self.delegate?.onLyricChanged(self.data)
                }
            }
        }else {
            //直接回调代理
            delegate?.onLyricChanged(data)
        }
    }
    
    /// 暂停
    func pause() {
        //更改状态
        status = .pause
        
        //暂停
        player.pause()
        
        //移除监听器
        removeListeners()
        
        //回调代理
        delegate?.onPaused(data!)
        
        //停止进度分发定时器
        stopPublishProgress()
    }
    
    /// 继续播放
    func resume() {
        //更改播放状态
        status = .playing
        
        //播放
        player.play()
        
        //设置监听器
        initListeners()
        
        //回调代理
        delegate?.onPlaying(data!)
        
        //启动进度分发定时器
        startPublishProgress()
    }
    
    /// 是否在播放
    ///
    /// - Returns: <#return value description#>
    func isPlaying() -> Bool {
        return status == .playing
    }
    
    /// 从指定位置开始播放
    ///
    /// - Parameter value: <#value description#>
    func seekTo(_ value:Float) {
        let positionTime = CMTime(seconds: Double(value), preferredTimescale: 1)
        player.seek(to: positionTime)
    }
    
    /// 开启进度回调通知
    func startPublishProgress() {
        //判断是否启动了
        if let _  = playTimeObserve {
            //已经启动了
            return
        }
        
        //1/60
        //16毫秒执行一次
        playTimeObserve = player.addPeriodicTimeObserver(forInterval: CMTime(value: CMTimeValue(1.0), timescale: 60), queue: DispatchQueue.main, using: { time in
            
            //判断是否有代理
            guard let delegate = self.delegate else {
                //没有回调
                //停止定时器
                self.stopPublishProgress()

                return
            }
            
            //获取当前音乐播放时间
            self.data!.progress = Float(CMTimeGetSeconds(time))
            
            print("MusicPlayerManager startPublishProgress progress:\(self.data.progress),\(self.data.duration)")
            
            //回调代理
            delegate.onProgress(self.data)
        })
        
        
        
    }
    
    /// 停止进度分发
    func stopPublishProgress() {
        if let playTimeObserve = playTimeObserve {
            player.removeTimeObserver(playTimeObserve)
            self.playTimeObserve = nil
        }
    }
}

/// 播放状态
///
/// - none: 没有播放
/// - playing: 播放中
/// - pause: 暂停中
/// - error: 播放失败了
enum PlayStatus {
    case none
    case playing
    case pause
    case error
}

/// 播放代理
protocol MusicPlayerDelegate {
    
    /// 播放器准备完毕了
    /// 可以获取到音乐总时长
    ///
    /// - Parameter data: <#data description#>
    func onPrepared(_ data:Song)
    
    /// 暂停了
    ///
    /// - Parameter data: <#data description#>
    func onPaused(_ data:Song)
    
    /// 正在播放
    ///
    /// - Parameter data: <#data description#>
    func onPlaying(_ data:Song)
    
    /// 进度回调
    ///
    /// - Parameter data: <#data description#>
    func onProgress(_ data:Song)
    
    /// 歌词信息改变了
    ///
    /// - Parameter data: <#data description#>
    func onLyricChanged(_ data:Song)
}
